package aryn.framework.display;

import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Shape;
import java.awt.Toolkit;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;

import aryn.framework.display.drawable.Drawable;
import aryn.framework.display.drawable.DrawingButton;
import aryn.framework.display.drawable.DrawingImage;
import aryn.framework.display.drawable.DrawingShape;
import aryn.framework.display.drawable.DrawingString;
import aryn.framework.display.drawable.Drawable.Priority;
import aryn.framework.graphic.Load;

public class MyCanvas extends Canvas {
	private static final long serialVersionUID = 1L;
	private static int count;
	private static final int FRAME_BORDER_X = 8;
	private static final int FRAME_BORDER_Y = 30;
	
	private int id;
	private BufferStrategy strategy;
	private Thread renderTask;
	private List<Drawable> drawables = new ArrayList<Drawable>();
	private List<Drawable> toAdd = new ArrayList<Drawable>();
	private List<Long> toRemove = new ArrayList<Long>();
	private int[] indexes = new int[5];
	private CursorsHandler cursors = new CursorsHandler(20);
	private MyMouseInfo mouseInfo;
	private MyCanvasLogic logic;
	
	static {
		 System.setProperty("sun.java2d.transaccel", "True");
		 System.setProperty("sun.java2d.opengl", "True");
		 System.setProperty("sun.java2d.d3d", "True");
		// System.setProperty("sun.java2d.ddforcevram", "True");
	}
	
	public MyCanvas(final int x, final int y, final int frameDimX, final int frameDimY) {	
		this.id = ++count;
		this.setVisible(true);
		this.setPreferredSize(new Dimension(frameDimX, frameDimY));
		this.setSize(frameDimX,frameDimY); 	// 16,38 = window borders
		this.setIgnoreRepaint(true);
		this.addMouseMotionListener();
		this.setLocation(FRAME_BORDER_X + x, FRAME_BORDER_Y + y);
	}
	
	public MyCanvas() {
		this(0, 0, 800, 600);
	}
	
	
	  // ******************* \\
	 //	   PUBLIC METHODS	  \\
	// *********************** \\
	
	public void setDrawSelection(boolean drawSelection) {
		mouseInfo.setDrawSelection(drawSelection);
	}
	
	public MyMouseInfo getMouseInfo() {
		return this.mouseInfo;
	}

	public void setLogic(MyCanvasLogic logic) {
		this.logic = logic;
	}

	public boolean equals(Object obj) {
		if (obj instanceof MyCanvas) {
			MyCanvas canvas = (MyCanvas) obj;
			if (canvas.id == this.id)
				return true;
		}
		return false;
	}
	
	public long addCursor(String cursorPath, int pointX, int pointY, String cursorName) {
		return cursors.addCursor(cursorPath, pointX, pointY, cursorName);
	}
	
	public long addCursor(String cursorPath) {
		return cursors.addCursor(cursorPath);
	}
	
	public void changeCursor(int cursorId) {
		this.setCursor(cursors.getCursor(cursorId));
	}
	
	public void setCursorsSize(int size) {
		cursors.setCursorsSize(size);
	}
	
	
	  // ******************* \\
	 //	   	  DRAWABLE		  \\
	// *********************** \\
	
	public Point getDrawablePosition(long id) {
		for(int i = 0; i < drawables.size(); i++)
			if(drawables.get(i).getId() == id)
				return new Point(drawables.get(i).getX(), drawables.get(i).getY());		
		throw new RuntimeException("resource with id = "+id+" does not exist.");
	}
	
	public Drawable getDrawable(long id) {
		for(int i = 0; i < drawables.size(); i++)
			if(drawables.get(i).getId() == id)
				return drawables.get(i);
		return null;
	}
	
	public List<Drawable> getDrawables() {
		return drawables;
	}
	
	public void setDrawablePosition(long id, int x, int y) {
		for(Drawable drawable : drawables)
			if(drawable.getId() == id) {		
				drawable.setX(x);
				drawable.setY(y);
			}
	}
	
	public void removeDrawable(long id) {
		toRemove.add(id);
	}
	
	  // ******************* \\
	 //	   	   BUTTONS		  \\
	// *********************** \\
	
	public long addButton(BufferedImage[] images, ButtonBorder[] borders, int x, int y) {
		DrawingButton button = new DrawingButton(images, borders, x, y, this);
		toAdd.add(button);
		return button.getId();
	}

	public long addButton(BufferedImage[] images, int x, int y) {
		DrawingButton button = new DrawingButton(images, x, y, this);
		toAdd.add(button);
		
		return button.getId();
	}
	
	public long addButton(String imagePath, int x, int y) {
		DrawingButton button = new DrawingButton(Load.bufferedImage(imagePath), x, y, this);
		toAdd.add(button);
		return button.getId();
	}
	
	
	  // ******************* \\
	 //	   	   IMAGES		  \\
	// *********************** \\
	
	public long addImage(String imagePath, int x, int y) {
		return addImage(imagePath, x, y, Priority.MIDDLE);
	}
	
	public long addImage(String imagePath, int x, int y, Priority priority) {
		DrawingImage img = new DrawingImage(Load.bufferedImage(imagePath), x, y, priority, this);
		toAdd.add(img);
		return img.getId();
	}
	
	public void setImage(long id, String imagePath) {
		for(int i = 0; i < drawables.size(); i++)
			if(drawables.get(i).getId() == id && drawables.get(i) instanceof DrawingImage) {
				DrawingImage img = (DrawingImage) drawables.get(i);
				img.setImage(Load.bufferedImage(imagePath));
			}
	}
	
	  // ******************* \\
	 //		   STRINGS		  \\
	// *********************** \\

	public long addString(String string, int x, int y, Priority priority, Color color) {
		DrawingString str = new DrawingString(string, x, y, priority, color, this);
		toAdd.add(str);
		return str.getId();
	}
	
	public long addString(String string, int x, int y) {
		return addString(string, x, y, Priority.FOREGROUND, Color.BLACK);
	}
	
	public void setString(long id, String newString) {
		for(int i = 0; i < drawables.size(); i++)
			if(drawables.get(i).getId() == id && drawables.get(i) instanceof DrawingString) {
				DrawingString str = (DrawingString) drawables.get(i);
				str.setString(newString);
			}
	}
	
	  // ******************* \\
	 //	   		SHAPES		  \\
	// *********************** \\
	
	public long addShape(int x, int y, Shape shape) {
		return addShape(x, y, shape, Priority.MIDDLE_FORE, null, false);
	}
	
	public long addShape(int x, int y, Shape shape, Priority priority, Color color, boolean fill) {
		DrawingShape shp = new DrawingShape(x, y, shape, priority, color, fill, this);
		toAdd.add(shp);
		return shp.getId();
	}
	
	public void setShape(long id, Shape newShape) {
		for(int i = 0; i < drawables.size(); i++)
			if(drawables.get(i).getId() == id && drawables.get(i) instanceof DrawingShape) {
				DrawingShape shp = (DrawingShape) drawables.get(i);
				shp.setShape(newShape);
			}
	}
	
	
	
	
	  // ******************* \\
	 //	   PACKAGE METHODS	  \\
	// *********************** \\
	
	void addMouseListener() {
		this.mouseInfo = new MyMouseInfo();
		this.mouseInfo.attachSelection(this);
		addMouseListener(new MouseListenerAdapter(this));
	}
	
	void addMouseMotionListener() {
		addMouseListener();
		addMouseMotionListener(new MouseMotionListenerAdapter(this));
	}
	
	void startGraphicThread() {
		this.createBufferStrategy(2);
		strategy = this.getBufferStrategy();

		renderTask = new Thread() {
			// no local variables in a constant loop.
			long	lasttime	= -1;
			long	msDelta		= 0;
			double	dt			= 0.01;
			long	time;
			double	deltaTime	= 0;
			double	accumulator	= 0;

			@Override
			public void run() {
				while (true) {
					synchronized (this) {
						time = System.nanoTime();

						// record the initial last time
						if (lasttime == -1)
							lasttime = time;
						
						// delta in milliseconds
						msDelta = (long) ((time - lasttime) * 0.000001d);
						
						// delta in seconds
						deltaTime = (time - lasttime) * 0.000000001d;

						// cap the number of update loops needed to keep up with high frame-rates.
						if (deltaTime > 0.25) {
							deltaTime = 0.25;
						}

						lasttime = time;

						accumulator += deltaTime;

						// accurate update rates, loop for each fixed 
						
						while (accumulator > deltaTime) {
							if(logic != null) {
								logic.update(dt);
							}
							for(Drawable drawable : drawables)
							if (drawable instanceof MyCanvasLogic) {
								MyCanvasLogic logic = (MyCanvasLogic) drawable;
								logic.update(dt);
							}
							accumulator -= dt;
						}
						mouseInfo.update();
						
						if(MyCanvas.this.isShowing())
							render();

						// if there is any time left due to quick updating and rendering then the thread can sleep
						long timeLeft = (10 - msDelta);
						if (timeLeft > 0) {
							try {
								this.wait(timeLeft);
							} catch (InterruptedException e) {
							}
						}
					}
				}
			}
		};
		renderTask.setPriority(renderTask.getPriority()+1);
		renderTask.start();
	}
	
	private void render() {
		Graphics2D bkG = (Graphics2D) strategy.getDrawGraphics();

//		bkG.setPaint(Color.GRAY);
//		bkG.fillRect(0, 0, getWidth(), getHeight());

		// draws
		for(Drawable drawable : drawables) {
			drawable.drawSelf(bkG);
		}
		
		// removes
		doRemove();
		
		// adds
		doAddDrawables();
		
		bkG.dispose();
		strategy.show();
		Toolkit.getDefaultToolkit().sync();
	}
	
	private void doRemove() {
		for(long id : toRemove)
			for(int i = 0; i < drawables.size(); i++)
				if(drawables.get(i).getId() == id) {
					for(int k = drawables.get(i).getPriority(); k < 5; k++)
						indexes[k] = indexes[k]-1;
					drawables.remove(i);
				}
		toRemove = new ArrayList<Long>();
	}
	
	private void doAddDrawables() {
		for(Drawable drawable : toAdd){
			int index = drawable.getPriority();
			drawables.add(indexes[index], drawable);
			for(int i = index; i < 5; i++)
				indexes[i] = indexes[i]+1;
		}
		toAdd = new ArrayList<Drawable>();
	}
	
	void stop() {
		renderTask.interrupt();
	}
}
